#include "boundaryPatch.hpp"
#include "meshTypes.hpp"
#include "structMeshFunctions.hpp"

namespace hms
{

BoundaryPatch::BoundaryPatch( std::string name ) : m_name { std::move(name) } {}

template<typename MeshType>
BoundaryPatch::BoundaryPatch(
	std::string name,
	const MeshBase<MeshType>& mesh,
	ArrayXi&& edges
) :
	BoundaryPatch(
		std::move(name),
		mesh,
		std::move(edges),
		getDomainCells(mesh, edges),
		getGhostCells (mesh, edges)
	)
{}

template<typename MeshType>
BoundaryPatch::BoundaryPatch(
	std::string name,
	const MeshBase<MeshType>& mesh,
	ArrayXi&& edges,
	ArrayXi&& domainCells,
	ArrayXi&& ghostCells
) :
	m_name        { std::move(name) },
	m_edges       { std::move(edges) },
	m_domainCells { std::move(domainCells) },
	m_ghostCells  { std::move(ghostCells) }
{
	setLocation(mesh);
}

auto BoundaryPatch::name() const -> const std::string& {
	return m_name;
}

auto BoundaryPatch::name() -> std::string& {
	return m_name;
}

auto BoundaryPatch::size() const -> Index {
	return m_edges.size();
}

auto BoundaryPatch::edges      () const -> const ArrayXi& {
	return m_edges;
}
auto BoundaryPatch::domainCells() const -> const ArrayXi& {
	assert( m_domainCells.size() == size() );
	return m_domainCells;
}
auto BoundaryPatch::ghostCells () const -> const ArrayXi& {
	assert( m_ghostCells.size() == size() );
	return m_ghostCells;
}

template<WhichCells whichCells>
auto BoundaryPatch::cells() const -> const ArrayXi& {
	using C = WhichCells;
	static_assert( whichCells == C::domain || whichCells == C::ghost );
	if constexpr ( whichCells == C::domain )
		return domainCells();
	else
		return ghostCells();
}

auto BoundaryPatch::cells(WhichCells whichCells) const -> const ArrayXi& {
	using C = WhichCells;
	assert( (whichCells == C::domain || whichCells == C::ghost) );
	if ( whichCells == C::domain )
		return cells<C::domain>();
	else
		return cells<C::ghost>();
}


bool BoundaryPatch::empty() const {
	return size() == 0;
}


template<typename MeshType>
void BoundaryPatch::updateCellIndices( const MeshBase<MeshType>& mesh ){
	m_domainCells = getDomainCells(mesh, m_edges);
	m_ghostCells = getGhostCells(mesh, m_edges);
	setLocation(mesh);
}

template<typename MeshType>
void BoundaryPatch::setEdgesAndCells(
	const MeshBase<MeshType>& mesh, ArrayXi&& edges
){
	m_edges = std::move(edges);
	updateCellIndices(mesh);
}

void BoundaryPatch::clearIndices(){
	m_edges = ArrayXi{};
	m_domainCells = ArrayXi{};
	m_ghostCells = ArrayXi{};
	m_normalSign = signalCode::uninitialised;
}

int BoundaryPatch::normalSign() const {
	assert( m_normalSign != signalCode::uninitialised );
	return m_normalSign;
}

auto BoundaryPatch::location() const -> BoundaryLocation {
	assert( m_normalSign != signalCode::uninitialised );
	return m_location;
}

namespace internal
{

template<WhichCells range, typename EdgeType>
Index getCell( EdgeBase<EdgeType>&& edge ){
	using C = WhichCells;
	static_assert( range == C::domain || range == C::ghost );
	if constexpr ( range == C::domain )
		return edge.domainCell();
	else
		return edge.ghostCell();
}

template<WhichCells range, typename MeshType>
ArrayXi getCells( const MeshBase<MeshType>& mesh, const ArrayXi& edges ){
	ArrayXi cells ( edges.size() );
	
	for ( Index i{0}; i<edges.size(); ++i ){
		cells(i) = getCell<range>( mesh.edge( edges(i) ) );
	}
	return cells;
}

} // namespace internal


template<typename MeshType>
ArrayXi BoundaryPatch::getDomainCells(
	const MeshBase<MeshType>& mesh, const ArrayXi& edges
) const {
	return internal::getCells<WhichCells::domain>(mesh, edges);
}

template<typename MeshType>
ArrayXXi BoundaryPatch::getGhostCells(
	const MeshBase<MeshType>& mesh, const ArrayXi& edges
) const {
	return internal::getCells<WhichCells::ghost>(mesh, edges);
}

template<typename MeshType>
void BoundaryPatch::setLocation( const MeshBase<MeshType>& mesh ){
	/* for unstructured meshes and for structured meshes at right and top sides */
	m_normalSign = 1;
	if constexpr ( std::is_base_of_v<StructMeshBase<MeshType>, MeshType> ){
		using B = BoundaryLocation;
		m_location = whereIs( *this, mesh.derived().size() );
		if (m_location == B::bottom || m_location == B::left )
			m_normalSign = -1;
	}
}


bool operator< ( const BoundaryPatch& a, const BoundaryPatch& b ){
	return a.name() < b.name();
}

template auto BoundaryPatch::cells<WhichCells::domain>() const -> const ArrayXi&;
template auto BoundaryPatch::cells<WhichCells::ghost >() const -> const ArrayXi&;

#define INSTANTIATE_FOR_ALL_MESHTYPES(MeshType) \
template BoundaryPatch::BoundaryPatch(std::string, const MeshBase<MeshType>&, ArrayXi&&); \
template BoundaryPatch::BoundaryPatch(std::string, const MeshBase<MeshType>&, \
	ArrayXi&&, ArrayXi&&, ArrayXi&& ); \
template void BoundaryPatch::updateCellIndices(const MeshBase<MeshType>&); \
template void BoundaryPatch::setEdgesAndCells(const MeshBase<MeshType>&, ArrayXi&&); \
template ArrayXi BoundaryPatch::getDomainCells(const MeshBase<MeshType>&, const ArrayXi&) const; \
template ArrayXXi BoundaryPatch::getGhostCells(const MeshBase<MeshType>&, const ArrayXi&) const; \
template void BoundaryPatch::setLocation( const MeshBase<MeshType>& );

#include "meshInstantiator.hpp"

} // end namespace hms